﻿#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <debug.h>
#include <os_api.h>
#include <device_api.h>
#include <lwip/sockets.h>

#define TASK_STACK_SIZE (20 * 1024)
static char _task_stack[TASK_STACK_SIZE];

#define APP_CMD_INIT 1
#define APP_CMD_SEND_HEART_BEAT 2
#define APP_CMD_SOCK_RECV_DATA 3
#define APP_CMD_SOCK_EVENT 4

typedef struct app_msg_s
{
    uint32_t msg;
    void *data;
} app_msg_t;

typedef struct app_data_s
{
    OSTaskRef task;
    OSTimerRef timer;
    OSMsgQRef cmdq;

    int sock;
} app_data_t;

static app_data_t *s_ad = NULL;

static int app_send_msg(app_data_t *ad, uint32_t msg_id, void *data)
{
    app_msg_t msg;

    memset(&msg, 0, sizeof(msg));
    msg.msg = msg_id;
    msg.data = data;
    if (OS_SUCCESS == OSAMsgQSend(ad->cmdq, sizeof(msg), (UINT8 *)&msg, OS_NO_SUSPEND))
    {
        return 0;
    }
    return -1;
}

static void app_init_timer_cb(UINT32 param)
{
    app_data_t *ad = (app_data_t *)param;
    app_send_msg(ad, APP_CMD_INIT, NULL);
}

static void app_send_heart_beat_timer_cb(UINT32 param)
{
    app_data_t *ad = (app_data_t *)param;
    app_send_msg(ad, APP_CMD_SEND_HEART_BEAT, NULL);
}

static void app_socket_event_cb(int s, int evt, u16_t len)
{
    LOG_PRINTF("%s: event=%d, len=%d", __func__, evt, len);
    if (evt == 0 && len > 0)
    {
        if (s_ad->sock >= 0 && s_ad->sock == s)
            app_send_msg(s_ad, APP_CMD_SOCK_RECV_DATA, NULL);
    }
    else if (evt == 0 && len == 0)
    {
        if (s_ad->sock >= 0 && s_ad->sock == s)
            app_send_msg(s_ad, APP_CMD_SOCK_EVENT, NULL);
    }
}

static int net_connect(int sock, const struct sockaddr *addr, socklen_t addrlen, int timeout_ms)
{
    int ret = 0;
    struct timeval tv;
    int sockflag, optval;
    socklen_t optlen;
    struct linger lin;
    fd_set wfds;

    sockflag = lwip_fcntl(sock, F_GETFL, 0);
    if (sockflag == -1)
    {
        ret = -1;
        goto clean;
    }
    ret = lwip_fcntl(sock, F_SETFL, sockflag | O_NONBLOCK);
    if (ret)
    {
        ret = -1;
        goto clean;
    }

    ret = lwip_connect(sock, addr, addrlen);
    if (!ret)
    {
        goto connect_ok;
    }

    optlen = sizeof(optval);
    ret = lwip_getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen);
    if (ret < 0)
    {
        ret = -1;
        goto clean;
    }
    if (optval != EINPROGRESS)
    {
        ret = -1;
        goto clean;
    }

    FD_ZERO(&wfds);
    FD_SET(sock, &wfds);
    tv.tv_sec = timeout_ms / 1000;
    tv.tv_usec = timeout_ms % 1000;
    ret = lwip_select(sock + 1, NULL, &wfds, NULL, &tv);
    if (ret != 1 || !FD_ISSET(sock, &wfds))
    {
        ret = -1;
        goto clean;
    }
    optlen = sizeof(optval);
    ret = lwip_getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, (socklen_t *)&optlen);
    if (ret < 0 || optval)
    {
        ret = -1;
        goto clean;
    }

connect_ok:
    sockflag = lwip_fcntl(sock, F_GETFL, 0);
    if (sockflag == -1)
    {
        ret = -1;
        goto clean;
    }
    ret = lwip_fcntl(sock, F_SETFL, sockflag & (~O_NONBLOCK));
    if (ret)
    {
        ret = -1;
        goto clean;
    }

    optval = 1;
    lwip_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
    lin.l_onoff = 1;
    lin.l_linger = 0;
    lwip_setsockopt(sock, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));
    ret = 0;
clean:
    return ret;
}

static int net_create(const char *server, int port)
{
    struct sockaddr_in addr;
    struct sockaddr_in6 addr6;
    struct addrinfo hints, *result = NULL, *rp;
    int sock = -1, ret = 0;
    char buf[32];

    if (!server || server[0] == '\0' || port <= 0 || port >= 65535)
    {
        ret = -1;
        goto clean;
    }

    memset(&addr, 0, sizeof(addr));
    memset(&addr6, 0, sizeof(addr6));
    if (inet_pton(AF_INET, server, &addr.sin_addr) == 1)
    {
        sock = lwip_socket_with_callback(AF_INET, SOCK_STREAM, 0, app_socket_event_cb);
        if (sock < 0)
        {
            ret = -1;
            goto clean;
        }
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        ret = net_connect(sock, (struct sockaddr *)&addr, sizeof(addr), 10000);
        if (ret < 0)
            goto clean;
    }
    else if (inet_pton(AF_INET6, server, &addr6.sin6_addr) == 1)
    {
        sock = lwip_socket_with_callback(AF_INET6, SOCK_STREAM, 0, app_socket_event_cb);
        if (sock < 0)
        {
            ret = -1;
            goto clean;
        }
        addr6.sin6_len = sizeof(addr6);
        addr6.sin6_family = AF_INET6;
        addr6.sin6_port = htons(port);
        ret = net_connect(sock, (struct sockaddr *)&addr6, sizeof(addr6), 10000);
        if (ret < 0)
            goto clean;
    }
    else
    {
        snprintf(buf, sizeof(buf), "%d", port);

        memset(&hints, 0, sizeof(hints));
        hints.ai_flags = 0;
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = 0;
        ret = lwip_getaddrinfo(server, buf, &hints, &result);
        if (ret)
        {
            ret = -1;
            goto clean;
        }
        for (rp = result; rp; rp = rp->ai_next)
        {
            sock = lwip_socket_with_callback(rp->ai_family, rp->ai_socktype, rp->ai_protocol, app_socket_event_cb);
            if (sock < 0)
            {
                continue;
            }
            ret = net_connect(sock, rp->ai_addr, rp->ai_addrlen, 10000);
            if (ret < 0)
            {
                lwip_close(sock);
                sock = -1;
                continue;
            }
            break;
        }
        freeaddrinfo(result);
        result = NULL;

        if (sock < 0)
        {
            ret = -1;
            goto clean;
        }
    }

    ret = sock;
clean:
    if (result)
        freeaddrinfo(result);
    if (ret < 0 && sock >= 0)
        lwip_close(sock);
    return ret;
}

static void app_socket_clean(app_data_t *ad)
{
    int sock;

    if (ad->sock >= 0)
    {
        LOG_PRINTF("%s:", __func__);
        sock = ad->sock;
        ad->sock = -1;
        lwip_close(sock);
    }
}

static void app_socket_reconnect(app_data_t *ad)
{
    app_socket_clean(ad);
    OSATimerStop(ad->timer);
    OSATimerStart(ad->timer, SEC2TICKS(30), 0, app_init_timer_cb, (UINT32)ad);
    LOG_PRINTF("%s:", __func__);
}

static void app_init(app_data_t *ad)
{
    int ret;
    char buf[64];
    size_t sz;

    LOG_PRINTF("%s: +", __func__);

    ad->sock = net_create("iot.yuge-info.com", 6005);
    if (ad->sock < 0)
    {
        LOG_PRINTF("%s: connect error", __func__);
        app_socket_reconnect(ad);
        goto clean;
    }
    LOG_PRINTF("%s: connect ok", __func__);

    strcpy(buf, "first connect message");
    sz = strlen(buf);
    ret = lwip_send(ad->sock, buf, sz, 0);
    if (ret != (int)sz)
    {
        LOG_PRINTF("%s: send first connect message error", __func__);
        app_socket_reconnect(ad);
        goto clean;
    }
    OSATimerStart(ad->timer, SEC2TICKS(30), 0, app_send_heart_beat_timer_cb, (UINT32)ad);

clean:
    LOG_PRINTF("%s: -", __func__);
}

static void app_send_heart_beat(app_data_t *ad)
{
    int ret;
    char buf[64];
    size_t sz;

    LOG_PRINTF("%s: +", __func__);

    if (ad->sock < 0)
        goto clean;

    strcpy(buf, "heart beat data");
    sz = strlen(buf);
    ret = lwip_send(ad->sock, buf, sz, 0);
    if (ret != (int)sz)
    {
        LOG_PRINTF("%s: send heart beat error", __func__);
        app_socket_reconnect(ad);
        goto clean;
    }
    LOG_PRINTF("%s: send heart beat OK", __func__);
    OSATimerStart(ad->timer, SEC2TICKS(30), 0, app_send_heart_beat_timer_cb, (UINT32)ad);

clean:
    LOG_PRINTF("%s: -", __func__);
}

static void app_socket_data(app_data_t *ad)
{
    int ret, len;
    uint16_t sz, cur = 0;
    char buf[3200];

    LOG_PRINTF("%s: +", __func__);

    if (ad->sock < 0)
        goto clean;

    ret = lwip_ioctl(ad->sock, FIONREAD, &sz);
    if (ret < 0 || !sz)
    {
        LOG_PRINTF("%s: no data?", __func__);
        ret = -1;
        app_socket_reconnect(ad);
        goto clean;
    }
    while (cur < sz)
    {
        len = sz - cur;
        if (len >= (int)sizeof(buf))
            len = (int)sizeof(buf) - 1;
        ret = lwip_recv(ad->sock, buf, len, 0);
        if (ret <= 0)
        {
            LOG_PRINTF("%s: recv error", __func__);
            ret = -1;
            app_socket_reconnect(ad);
            goto clean;
        }
        cur += ret;
        buf[ret] = '\0';
        LOG_PRINTF("%s: recv data: %s", __func__, buf);

        if (!strncmp(buf, "reconnect", 9))
        {
            LOG_PRINTF("%s: receive reconnect command", __func__);
            ret = -1;
            app_socket_reconnect(ad);
            goto clean;
        }
    }

clean:
    LOG_PRINTF("%s: -", __func__);
}

static void app_socket_event(app_data_t *ad)
{
    LOG_PRINTF("%s: +", __func__);
    app_socket_reconnect(ad);
    LOG_PRINTF("%s: -", __func__);
}

static void app_task(void *param)
{
    app_data_t *ad = (app_data_t *)param;
    int ret;
    app_msg_t msg;

    while (1)
    {
        memset(&msg, 0, sizeof(msg));
        ret = OSAMsgQRecv(ad->cmdq, (UINT8 *)&msg, sizeof(msg), OS_SUSPEND);
        ASSERT(ret == OS_SUCCESS);
        switch (msg.msg)
        {
        case APP_CMD_INIT:
            app_init(ad);
            break;
        case APP_CMD_SEND_HEART_BEAT:
            app_send_heart_beat(ad);
            break;
        case APP_CMD_SOCK_RECV_DATA:
            app_socket_data(ad);
            break;
        case APP_CMD_SOCK_EVENT:
            app_socket_event(ad);
            break;
        default:
            break;
        }
    }
}

int main()
{
    int ret;
    app_data_t *ad;

    dev_SetAppVersion("TCP-V1.0");

    ad = malloc(sizeof(*ad));
    ASSERT(ad);
    memset(ad, 0, sizeof(*ad));

    ad->sock = -1;

    s_ad = ad;

    ret = OSATimerCreate(&ad->timer);
    ASSERT(ret == OS_SUCCESS);

    ret = OSAMsgQCreate(&ad->cmdq, "app", sizeof(app_msg_t), 10, OS_FIFO);
    ASSERT(ret == OS_SUCCESS);

    ret = OSATaskCreate(&ad->task, _task_stack, TASK_STACK_SIZE, OS_APP_PRIORITY, "app", app_task, ad);
    ASSERT(ret == OS_SUCCESS);

    ret = OSATimerStart(ad->timer, SEC2TICKS(10), 0, app_init_timer_cb, (UINT32)ad);
    ASSERT(ret == OS_SUCCESS);
}
